import { BinaryToTextEncoding, createHash } from 'crypto'
import fs from 'fs'
import fsp from 'fs/promises'
import path from 'path'
import { CustomPluginOptions, PluginContext, ResolvedId } from 'rollup'
const MAGIC = Buffer.from([0x00, 0x61, 0x73, 0x6d])
const MAGIC_LENGTH = MAGIC.length

export const getSourcePath = async (
  _this: PluginContext,
  id: string,
  importer?: string,
  extensions: string[] = [],
  options?: { custom?: CustomPluginOptions; isEntry: boolean }
) => {
  let resolved: ResolvedId | null | undefined

  if (/[\0\?]|..+:/.test(id)) {
    return
  }

  extensions = extensions.filter((ext) => ext).map((ext) => (ext.startsWith('.') ? ext : '.' + ext))
  extensions.unshift('')

  const opts = {
    ...options,
    skipSelf: true,
  }

  for (const ext of extensions) {
    resolved =
      (await _this.resolve(ext ? addExtension(id, ext) : id, importer, opts)) ??
      (await _this.resolve(path.join(id, 'index' + ext), importer, opts))
    if (resolved) break
  }

  if (resolved) {
    ;({ id } = resolved)
    if (path.isAbsolute(id)) {
      return path.normalize(id)
    } else {
      return resolveModuleId(id)
    }
  }

  for (const ext of extensions) {
    const resolveId =
      resolveModuleId(ext ? addExtension(id, ext) : id) ??
      resolveModuleId(path.join(id, 'index' + ext))
    if (resolveId) return resolveId
  }

  return resolveModuleId(id)
}

export const isWasmSync = (fileName: string) => {
  let fd: number
  try {
    fd = fs.openSync(fileName, 'r')
    const buffer = Buffer.alloc(MAGIC_LENGTH, 0)
    fs.readSync(fd, buffer, 0, MAGIC_LENGTH, 0)
    return isWasmFromBuffer(buffer)
  } catch (error) {
    return false
  }
}

export const isWasm = async (fileName: string) => {
  let handle: fsp.FileHandle
  try {
    handle = await fsp.open(fileName, 'r')
    const buffer = Buffer.alloc(MAGIC_LENGTH, 0)
    await handle.read(buffer, 0, MAGIC_LENGTH, 0)
    return isWasmFromBuffer(buffer)
  } catch (error) {
    return false
  }
}

export const isWasmFromString = (str: string) => {
  return isWasmFromBuffer(Buffer.from(str.slice(0, MAGIC_LENGTH)))
}

export const isWasmFromBuffer = (buffer: Buffer) => {
  return MAGIC.compare(buffer, 0, MAGIC_LENGTH) === 0
}

export const resolveModuleId = (id: string) => {
  try {
    return require.resolve(id)
  } catch {}
}

export const safeRequire = <T extends string>(id: T) => {
  try {
    return require(id)
  } catch {}
}

export const escape = (str: string) =>
  str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d')
export const hashString = (
  source: string,
  encoding: BinaryToTextEncoding = 'hex',
  length?: number
) => {
  const hash = createHash('sha1').update(source).digest(encoding)
  return length ? hash.slice(0, length).padStart(length, '_') : hash
}

export const toArray = <T = any>(data: any): T[] => {
  if (Array.isArray(data)) return data
  if (data == null) return []
  if (typeof data !== 'object') return [data]
  if (Symbol.iterator in data || 'length' in data) return Array.from(data as any)
  return [data]
}

export const removeExtension = (name: string, ext?: string) => {
  if (!ext) return name.replace(/\.[^\.\\\/:;\?\*]+$/, '')
  if (!ext.startsWith('.')) {
    ext = '.' + ext
  }
  if (!name.endsWith(ext)) {
    return name
  }
  return name.slice(0, name.length - ext.length)
}

export const addExtension = (name: string, ext: string) => {
  if (!ext) return name.replace(/\.[^\.\\\/:;\?\*]+$/, '')
  if (!ext.startsWith('.')) {
    ext = '.' + ext
  }
  if (name.endsWith(ext)) {
    return name
  }
  return name + ext
}

export const getResolveUrl = (path: string, URL = 'URL') => `new ${URL}(${path}).href`

export const getRelativeUrlFromDocument = (relativePath: string, umd = false) =>
  getResolveUrl(
    `'${relativePath}', ${
      umd ? `typeof document === 'undefined' ? location.href : ` : ''
    }document.currentScript && document.currentScript.src || document.baseURI`
  )

export const getUrlFromDocument = (chunkId: string, umd = false) =>
  `${
    umd ? `typeof document === 'undefined' ? location.href : ` : ''
  }(document.currentScript && document.currentScript.src || new URL('${chunkId}', document.baseURI).href)`
